/*
 * Copyright (c) 2007, Olof Naessen and Per Larsson
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *    * Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 *    * Neither the name of the Darkbits nor the names of its contributors may be 
 *      used to endorse or promote products derived from this software without 
 *      specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "level.hpp"

#include <fstream>
#include <iostream>
#include <set>
#include <sstream>

#include "desertbackground.hpp"
#include "sewersbackground.hpp"
#include "prisonbackground.hpp"
#include "levelhandler.hpp"
#include "game.hpp"
#include "resourcehandler.hpp"

Level::Level()
:mTileMap(0),
mBall(0),
mScroll(0),
mLastEnter(false),
mCollectedStars(0),
mGoalReached(false),
mCompleted(false),
mTime(0),
mBackground(0)
{
    mGui = new gcn::Gui();
    mAllegroGraphics = new gcn::AllegroGraphics();
    mAllegroImageLoader = new gcn::AllegroImageLoader();

    mGui->setGraphics(mAllegroGraphics);
    gcn::Image::setImageLoader(mAllegroImageLoader);

    mTop = new gcn::Container();
    mTop->setSize(320, 240);
    mGui->setTop(mTop);

    mTop->setSize(320, 240);
    mTop->setOpaque(false);

    mStarsLabel = new gcn::Label("STARS 0");
    mTop->add(mStarsLabel, 0, 0);

    mTimeLabel = new gcn::Label("TIME 00:00:00");
    mTop->add(mTimeLabel, 260, 0);

	mDataWriter = new DataWriter();
	mDataWriter->setSize(320, 70);
	mTop->add(mDataWriter, 0, 150);

    mReady = new gcn::Label("READY");
    mReady->setVisible(false);
    mReady->setHeight(20);
    mTop->add(mReady, 150, 105);
    mSteady = new gcn::Label("STEADY");
    mSteady->setVisible(false);
    mSteady->setHeight(20);
    mTop->add(mSteady, 150, 105);
    mRoll = new gcn::Label("ROLL!");
    mRoll->setVisible(false);
    mRoll->setHeight(20);
    mTop->add(mRoll, 150, 105);

	mBeepSample = ResourceHandler::getInstance()->getSample("beep.wav");
}

Level::~Level()
{
    delete mTimeLabel;
    delete mStarsLabel;
    delete mTop;
    delete mAllegroImageLoader;
    delete mAllegroGraphics;
    delete mGui;
}

void Level::load(const std::string& filename)
{
    std::string realFilename = ResourceHandler::getInstance()->getRealFilename(filename);
    mGoalReached = false;
    mCompleted = false;
    mCollectedStars = 0;

    if (mTileMap != 0)
    {
        delete mTileMap;
    }

    if (mBall != 0)
    {
        delete mBall;
    }

    std::ifstream file;
    file.open(realFilename.c_str(), std::ios::in);

    if (!file.is_open())
    {
        throw std::string("Level file could not be opened! ") + realFilename;
    }

    int length;
    file >> length;

    mTileMap = new TileMap(length);
 
    std::string line;
    int lineNumber;

    int ballx = 0;
    int bally = 0;

    // Remove first line
    getline(file, line);

	for (lineNumber = 0; lineNumber < 15; lineNumber++)
	{
        getline(file, line);

        unsigned int i;
        for (i = 0; i < line.size(); i++)
        {
            int x = i * 16;
            int y = lineNumber * 16;

            if (line[i] == 'B')
            {
                ballx = x;
                bally = y;
                mTileMap->setTile(0, x, y);
            }
            else if (line[i] == 'x')
            {
                mTileMap->setTile(10, x, y);
            }
            else if (line[i] == 'd')
            {
                mTileMap->setTile(11, x, y);
            }
            else if (line[i] == 'E')
            {
                mTileMap->setTile(12, x, y);
            }
            else if (line[i] == 'P')
            {
                mTileMap->setTile(13, x, y);
            }
            else if (line[i] == 'Q')
            {
                mTileMap->setTile(14, x, y);
            }
            else if (line[i] == 'D')
            {
                mTileMap->setTile(15, x, y);
            }
            else if (line[i] == 'G')
            {
                mTileMap->setTile(16, x, y);
            }
            else
            {
                unsigned int type = (unsigned int)(line[i] - '0');
                mTileMap->setTile(type, x, y);
            }
        }
    }

	while(!file.eof()) {
		getline(file, line);
		if (!line.empty()) {
			mIntroText.push_back(line);
		}
	}

    file.close();
	mFrame = 0;

    mBall = new Ball(ballx, bally);

	if (mIntroText.empty()) {
		mDataWriter->setText("");
	} else {
		mDataWriter->setText(mIntroText.front());
	}

    mTileMap->connectDoorButtonsAndDoors();
    mTime = 0;

    if (mBackground == 0)
    {
        delete mBackground;
    }

	if (LevelHandler::getInstance()->getLevel(LevelHandler::getInstance()->getCurrentLevel())->tileSet == 1) {
		mBackground = new SewersBackground();
	} else if (LevelHandler::getInstance()->getLevel(LevelHandler::getInstance()->getCurrentLevel())->tileSet == 2) {
		mBackground = new PrisonBackground();
	} else {
		mBackground = new DesertBackground();
	}
	mScroll = mBall->getCenterX() - 160;
	
	if (mScroll < 0)
    {
        mScroll = 0;
    }

    if (mScroll > mTileMap->getLength()*16 - 320)
    {
        mScroll = mTileMap->getLength()*16 - 320;
    }

	mBall->freeze(50);
}

void Level::draw(BITMAP* buffer)
{
    mBackground->draw(buffer, mScroll);
    mBall->draw(buffer, mScroll);
	mTileMap->draw(buffer, mFrame / 5, mScroll);
    mAllegroGraphics->setTarget(buffer);
    mGui->draw();
	rectfill(buffer, 100, 2, 100 + mBall->getMetalBarPosition(), 5, makecol(mBall->isMetal() ? 255 : 160, 0, 0));
	rect(buffer, 100, 1, 201, 6, 0);
}

void Level::logic()
{
    if (mGoalReached)
    {
        // TODO Display complete screen for a few seconds.
        mCompleted = true;
        return;
    }

	mFrame++;
    mBackground->logic();
	mGui->logic();
	mTileMap->logic();

	if (mIntroText.empty())
	{
		gameLogic();
	}
	else
	{
		introLogic();
	}
}

void Level::introLogic()
{
    if (keypressed() && (readkey() >> 8) == KEY_ESC)
    {
       mDataWriter->setText("");
       mIntroText.clear();
    }

	bool enter = key[KEY_ENTER];
	if (mDataWriter->isEverythingWritten()) {
		if ((enter && !mLastEnter) || mDataWriter->getText().length() == 0) {
			mDataWriter->setLettersWritten(0);
			mIntroText.pop_front();
			if (mIntroText.empty()) {
				mDataWriter->setText("");
			} else {
				mDataWriter->setText(mIntroText.front());
			}
		}
	} else if (mFrame % 3 == 0 || enter) {
		mDataWriter->setLettersWritten(mDataWriter->getLettersWritten() + 1);
		if (mDataWriter->getText().length() == mDataWriter->getLettersWritten()) {
			play_sample(mBeepSample, 150, 128, 800, 0);
		}
		else if (mDataWriter->getText().at(mDataWriter->getLettersWritten()) != ' ') {
			play_sample(mBeepSample, 100, 128, 1000, 0);
		}
	}
	mLastEnter = enter;
}

void Level::gameLogic()
{
    if (keypressed() && (readkey() >> 8) == KEY_ESC)
    {
        mBall->kill();    
    }

    mTime++;
    mReady->setVisible(mTime <= 25);
    mSteady->setVisible(mTime > 25 && mTime <= 50);
    mRoll->setVisible(mTime > 50 && mTime <= 75);
  

    mBall->logic(mTileMap);

	int targetScroll = mBall->getCenterX() - 160;
	int deltaScroll = targetScroll - mScroll;
	if (deltaScroll > 5) {
		deltaScroll = 5;
	}
	if (deltaScroll < -5) {
		deltaScroll = -5;
	}
	mScroll += deltaScroll;

    if (mScroll < 0)
    {
        mScroll = 0;
    }

    if (mScroll > mTileMap->getLength()*16 - 320)
    {
        mScroll = mTileMap->getLength()*16 - 320;
    }

    handleCollision();

    std::string stars;
    std::ostringstream starsos(stars);
    starsos << "~ * "  << mCollectedStars;
    mStarsLabel->setCaption(starsos.str());
    mStarsLabel->adjustSize();

    std::string time;
    std::ostringstream timeos(time);
    timeos << "TIME ";

    int minutes = (mTime / 50) / 60;
    
    if (minutes < 10)
    {
        timeos << "0" << minutes << ":";
    }
    else
    {
        timeos << minutes << ":";
    }
    
    int seconds = (mTime / 50) % 60;

    if (seconds < 10)
    {
        timeos << "0" << seconds << ":";
    }
    else
    {
        timeos << seconds << ":";
    }

    int hundredths = (mTime * 2) % 100;

    if (hundredths < 10)
    {
        timeos << "0" << hundredths;
    }
    else
    {
        timeos << hundredths;
    }

    mTimeLabel->setCaption(timeos.str());
    mTimeLabel->adjustSize();
}

void Level::handleCollision()
{
    std::set<Tile*> tiles;

    Tile* upperLeftCornerTile = mTileMap->getTileAtPixel(mBall->getCenterX() - 8, 
                                                         mBall->getCenterY() - 8);
    if (upperLeftCornerTile->isSolid())
    {
        tiles.insert(upperLeftCornerTile);
    }

    Tile* upperRightCornerTile = mTileMap->getTileAtPixel(mBall->getCenterX() + 7, 
                                                          mBall->getCenterY() - 8);
    if (upperRightCornerTile->isSolid())
    {
        tiles.insert(upperRightCornerTile);
    }

    Tile* lowerLeftCornerTile = mTileMap->getTileAtPixel(mBall->getCenterX() - 8, 
                                                         mBall->getCenterY() + 7);
    if (lowerLeftCornerTile->isSolid())
    {
        tiles.insert(lowerLeftCornerTile);
    }
  
    Tile* lowerRightCornerTile = mTileMap->getTileAtPixel(mBall->getCenterX() + 7, 
                                                          mBall->getCenterY() + 7);
    if (lowerRightCornerTile->isSolid())
    {
        tiles.insert(lowerRightCornerTile);
    }
        
    Tile* aboveTile = mTileMap->getTileAtPixel(mBall->getCenterX(), 
                                               mBall->getCenterY() - 9);
    if (aboveTile->isSolid())
    {
        tiles.insert(aboveTile);
    }

    Tile* belowTile = mTileMap->getTileAtPixel(mBall->getCenterX(), 
                                               mBall->getCenterY() + 8);
    if (belowTile->isSolid())
    {
        tiles.insert(belowTile);
    }

    Tile* leftTile = mTileMap->getTileAtPixel(mBall->getCenterX() - 9, 
                                               mBall->getCenterY());
    if (leftTile->isSolid())
    {
        tiles.insert(leftTile);
    }

    Tile* rightTile = mTileMap->getTileAtPixel(mBall->getCenterX() + 8, 
                                               mBall->getCenterY());
    if (rightTile->isSolid())
    {
        tiles.insert(rightTile);
    }

    Tile* middleTile1 = mTileMap->getTileAtPixel(mBall->getCenterX() + 4, 
                                                 mBall->getCenterY() + 4);
    tiles.insert(middleTile1);
    Tile* middleTile2 = mTileMap->getTileAtPixel(mBall->getCenterX() + 4, 
                                                 mBall->getCenterY() - 4);
    tiles.insert(middleTile2);
    Tile* middleTile3 = mTileMap->getTileAtPixel(mBall->getCenterX() - 4, 
                                                 mBall->getCenterY() + 4);
    tiles.insert(middleTile3);
    Tile* middleTile4 = mTileMap->getTileAtPixel(mBall->getCenterX() - 4, 
                                                 mBall->getCenterY() - 4);
    tiles.insert(middleTile4);

    std::set<Tile*>::iterator it;
    for (it = tiles.begin(); it != tiles.end(); it++)
    {
        (*it)->collision(this, mBall);
    }
}

bool Level::isCompleted()
{
    return mCompleted;
}

bool Level::isFailed() {
	return mBall->getState() == Ball::DEAD;
}
